﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Threading;
using FFmpegWrappedSharpRecorder.Models;
using FFmpegWrappedSharpRecorder.Tools;
using Prism.Commands;
using Prism.Mvvm;
using Application = System.Windows.Application;
using MessageBox = HandyControl.Controls.MessageBox;
using OpenFileDialog = Microsoft.Win32.OpenFileDialog;

// ReSharper disable once IdentifierTypo
namespace FFmpegWrappedSharpRecorder.ViewModels
{
    internal class MainWindowViewModel : BindableBase
    {
        // ReSharper disable once IdentifierTypo
        public DelegateCommand BrowseFFmpegPathCommand { get; }
        public DelegateCommand BrowseVideoPathCommand { get; }
        public DelegateCommand RefreshDevicesCommand { get; }
        public DelegateCommand StartRecordCommand { get; }
        public DelegateCommand StopRecordCommand { get; }

        #region 依赖属性

        public ObservableCollection<string> CameraList { get; }
        public ObservableCollection<string> MicList { get; }
        public ObservableCollection<string> EncoderList { get; }
        public ObservableCollection<string> HardwareAccelerationMethodList { get; }
        public ObservableCollection<VideoOption> VideoOptionList { get; }

        // ReSharper disable once IdentifierTypo
        public string? FFmpegPath
        {
            get => _ffmpegPath;
            set => SetProperty(ref _ffmpegPath, value);
        }

        public string? VideoPath
        {
            get => _videoPath;
            set => SetProperty(ref _videoPath, value);
        }

        public string? CurrentEncoder
        {
            get => _currentEncoder;
            set => SetProperty(ref _currentEncoder, value);
        }

        public string? CurrentHardwareAccelerationMethod
        {
            get => _currentHardwareAccelerationMethod;
            set => SetProperty(ref _currentHardwareAccelerationMethod, value);
        }

        public string? VideoInfo
        {
            get => _videoInfo;
            set => SetProperty(ref _videoInfo, value);
        }

        public string? Log
        {
            get => _log;
            set => SetProperty(ref _log, value);
        }

        public bool RecordScreen
        {
            get => _recordScreen;
            set => SetProperty(ref _recordScreen, value);
        }

        public bool StartRecordEnable
        {
            get => _startRecordEnable;
            set => SetProperty(ref _startRecordEnable, value);
        }

        public bool StopRecordEnable
        {
            get => _stopRecordEnable;
            set => SetProperty(ref _stopRecordEnable, value);
        }

        public int SelectedCameraIndex
        {
            get => _selectedCameraIndex;
            set
            {
                SetProperty(ref _selectedCameraIndex, value);
                UpdateResolution();
            }
        }

        public int SelectedMicIndex
        {
            get => _selectedMicIndex;
            set => SetProperty(ref _selectedMicIndex, value);
        }

        public int SelectedEncoderIndex
        {
            get => _selectedEncoderIndex;
            set => SetProperty(ref _selectedEncoderIndex, value);
        }

        public int SelectedHardwareAccelerationMethodIndex
        {
            get => _selectedHardwareAccelerationMethodIndex;
            set
            {
                SetProperty(ref _selectedHardwareAccelerationMethodIndex, value);
                _hardwareAccelerationMethod =
                    value > 0 && value < HardwareAccelerationMethodList.Count
                        ? HardwareAccelerationMethodList[value]
                        : string.Empty;
            }
        }

        public int SelectedVideoOptionIndex
        {
            get => _selectedVideoOptionIndex;
            set => SetProperty(ref _selectedVideoOptionIndex, value);
        }

        // ReSharper disable once IdentifierTypo
        private string? _ffmpegPath;
        private string? _videoPath;
        private string? _currentEncoder;
        private string? _currentHardwareAccelerationMethod;
        private string? _videoInfo;
        private string? _log;
        private bool _recordScreen;
        private bool _startRecordEnable = true;
        private bool _stopRecordEnable;
        private int _selectedCameraIndex = -1;
        private int _selectedMicIndex = -1;
        private int _selectedEncoderIndex = -1;
        private int _selectedHardwareAccelerationMethodIndex = -1;
        private int _selectedVideoOptionIndex = -1;

        #endregion

        private readonly DispatcherTimer _timer;
        private DateTime _recordStartTime;

        private string _hardwareAccelerationMethod = string.Empty;

        public MainWindowViewModel()
        {
            BrowseFFmpegPathCommand = new DelegateCommand(OnBrowseFFmpegPathAsync);
            BrowseVideoPathCommand = new DelegateCommand(OnBrowseVideoPath);
            RefreshDevicesCommand = new DelegateCommand(OnRefreshDevicesAsync);
            StartRecordCommand = new DelegateCommand(OnStartRecord);
            StopRecordCommand = new DelegateCommand(OnStopRecordAsync);

            CameraList = new ObservableCollection<string>();
            MicList = new ObservableCollection<string>();
            EncoderList = new ObservableCollection<string>();
            HardwareAccelerationMethodList = new ObservableCollection<string>();
            VideoOptionList = new ObservableCollection<VideoOption>();

            _timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
            _timer.Tick += OnTimerTick;

            var info = new DirectoryInfo(Assembly.GetExecutingAssembly().Location);
            var videoPath = Path.Combine(info.Parent?.FullName ?? string.Empty, "Videos");
            if (!Directory.Exists(videoPath))
            {
                Directory.CreateDirectory(videoPath);
            }

            VideoPath = videoPath;

            // ReSharper disable once IdentifierTypo
            var ffmpegPath = Path.Combine(info.Parent?.FullName ?? string.Empty, "ffmpeg", "ffmpeg.exe");
            if (File.Exists(ffmpegPath))
            {
                Task.Run(async () =>
                {
                    // ReSharper disable once AsyncVoidLambda
                    await Application.Current.Dispatcher.BeginInvoke(new Action(async () =>
                    {
                        FFmpegPath = ffmpegPath;
                        await RefreshDevices();
                    }));
                });
            }
        }

        public async Task DisposeAsync()
        {
            await Task.Run(FFmpegHelper.StopRecord);
        }

        // ReSharper disable once IdentifierTypo
        private async void OnBrowseFFmpegPathAsync()
        {
            var openFileDialog = new OpenFileDialog()
            {
                Filter = @"app files (*.exe)|*.exe|All files (*.*)|*.*" //选择的文件类型的筛选器
            };
            if (openFileDialog.ShowDialog() == true)
            {
                var safeFileName = openFileDialog.SafeFileName.ToLower();
                if (string.IsNullOrWhiteSpace(safeFileName) || !safeFileName.EndsWith("ffmpeg.exe"))
                {
                    MessageBox.Show(@"请选择ffmpeg.exe路径", @"提示");
                    return;
                }

                FFmpegPath = openFileDialog.FileName;
                await RefreshDevices();
            }
        }

        private void OnBrowseVideoPath()
        {
            using var folderBrowserDialog = new FolderBrowserDialog();
            if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
            {
                VideoPath = folderBrowserDialog.SelectedPath;
            }
        }

        private async void OnRefreshDevicesAsync()
        {
            if (string.IsNullOrWhiteSpace(FFmpegPath))
            {
                AddLog(@"获取摄像头列表失败：请选择FFmpeg路径");
                return;
            }

            if (!File.Exists(FFmpegPath))
            {
                AddLog(@"获取摄像头列表失败：FFmpeg路径不存在");
                return;
            }

            await RefreshDevices();
        }

        private async Task RefreshDevices()
        {
            await UpdateDevicesAsync();
            await UpdateEncoderListAsync();
            await UpdateHardwareAccelerationMethodListAsync();
        }

        private void OnStartRecord()
        {
            if (string.IsNullOrWhiteSpace(FFmpegPath) || !File.Exists(FFmpegPath))
            {
                AddLog(@"请选择ffmpeg路径");
                return;
            }

            if (SelectedCameraIndex < 0 || SelectedCameraIndex >= CameraList.Count)
            {
                AddLog("请选择摄像头");
                return;
            }

            if (SelectedMicIndex < 0 || SelectedMicIndex >= MicList.Count)
            {
                AddLog("请选择麦克风");
                return;
            }

            if (SelectedVideoOptionIndex < 0 || SelectedVideoOptionIndex >= VideoOptionList.Count)
            {
                AddLog("请选择视频分辨率");
                return;
            }

            var camera = CameraList[SelectedCameraIndex];
            var mic = MicList[SelectedMicIndex];
            var resolution = VideoOptionList[SelectedVideoOptionIndex].Resolution ?? string.Empty;
            var encoder = CurrentEncoder ?? string.Empty;
            var videoPath = VideoPath ?? string.Empty;
            var fps = 15; //Math.Max(videoOption.Fps, 15);
            var method = _hardwareAccelerationMethod;
            var ret = RecordScreen
                ? FFmpegHelper.StartScreenRecord(FFmpegPath, mic, encoder, videoPath, fps, method)
                : FFmpegHelper.StartRecord(FFmpegPath, camera, mic, resolution, encoder, videoPath, fps, method);
            if (!ret.Item1)
            {
                AddLog($"录制视频失败：{ret.Item2}");
                return;
            }

            _recordStartTime = DateTime.Now;
            _timer.Start();
            VideoInfo = @"正在录制视频：00:00:00";
            StartRecordEnable = false;
            StopRecordEnable = true;
            AddLog("开始录制视频");
        }

        private async void OnStopRecordAsync()
        {
            _timer.Stop();
            VideoInfo = @"正在停止录制视频";
            await Task.Run(FFmpegHelper.StopRecord);
            VideoInfo = string.Empty;
            StartRecordEnable = true;
            StopRecordEnable = false;
            AddLog($"完成视频录制：{FFmpegHelper.VideoPath}");
        }

        // ReSharper disable once IdentifierTypo
        private async Task UpdateDevicesAsync()
        {
            CameraList.Clear();
            MicList.Clear();
            SelectedCameraIndex = -1;
            SelectedMicIndex = -1;

            var ret = await Task.Run(() => FFmpegHelper.GetDeviceList(FFmpegPath ?? string.Empty));
            if (ret.Item1)
            {
                var cameraList = ret.Item2
                    .Where(t => t.DeviceType == DeviceType.Video)
                    .Select(t => t.DeviceName)
                    .ToList();
                var micList = ret.Item2
                    .Where(t => t.DeviceType == DeviceType.Audio)
                    .Select(t => t.DeviceName)
                    .ToList();

                CameraList.Clear();
                MicList.Clear();
                CameraList.AddRange(cameraList);
                MicList.AddRange(micList);
                SelectedCameraIndex = cameraList.Any() ? 0 : -1;
                SelectedMicIndex = micList.Any() ? 0 : -1;
                AddLog("获取设备列表成功");
            }
            else
            {
                AddLog($"获取设备列表失败：{ret.Item3}");
            }
        }

        private async Task UpdateEncoderListAsync()
        {
            var encoder = SelectedEncoderIndex >= 0 ? EncoderList[SelectedEncoderIndex] : string.Empty;
            EncoderList.Clear();
            SelectedEncoderIndex = -1;

            var ret = await Task.Run(() =>
                FFmpegHelper.GetEncoderList(FFmpegPath ?? string.Empty));
            if (ret.Item1)
            {
                var list = ret.Item2
                    .Where(t => !string.IsNullOrWhiteSpace(t.EncoderName))
                    .Select(t => t.EncoderName)
                    .ToList();
                EncoderList.Clear();
                EncoderList!.AddRange(list);

                var index = string.IsNullOrWhiteSpace(encoder) ? 0 : list.IndexOf(encoder);
                SelectedEncoderIndex = index >= 0 ? index : 0;

                AddLog("获取编码器成功");
            }
            else
            {
                AddLog($"获取编码器失败：{ret.Item3}");
            }
        }

        private async Task UpdateHardwareAccelerationMethodListAsync()
        {
            var method = SelectedHardwareAccelerationMethodIndex >= 0
                ? HardwareAccelerationMethodList[SelectedHardwareAccelerationMethodIndex]
                : "无";
            SelectedHardwareAccelerationMethodIndex = -1;
            HardwareAccelerationMethodList.Clear();

            var ret = await Task.Run(() =>
                FFmpegHelper.GetHardwareAccelerationMethodList(FFmpegPath ?? string.Empty));
            if (ret.Item1)
            {
                var list = ret.Item2
                    .Where(t => !string.IsNullOrWhiteSpace(t))
                    .ToList();
                list.Insert(0, "无");
                HardwareAccelerationMethodList.Clear();
                HardwareAccelerationMethodList.AddRange(list);

                var index = string.IsNullOrWhiteSpace(method) ? 0 : list.IndexOf(method);
                SelectedHardwareAccelerationMethodIndex = index >= 0 ? index : 0;

                AddLog("获取硬件加速方法成功");
            }
            else
            {
                HardwareAccelerationMethodList.Insert(0, "无");
                AddLog($"获取硬件加速方法失败：{ret.Item3}");
            }
        }

        private void UpdateResolution()
        {
            VideoOptionList.Clear();
            SelectedVideoOptionIndex = -1;

            if (SelectedCameraIndex < 0 || SelectedCameraIndex >= CameraList.Count)
            {
                return;
            }

            Task.Run(() =>
            {
                var camera = CameraList[SelectedCameraIndex];
                var ret = FFmpegHelper.GetVideoResolutionList(FFmpegPath ?? string.Empty, camera);
                Application.Current.Dispatcher.BeginInvoke(new Action<Tuple<bool, List<VideoOption>, string>>(tuple =>
                {
                    if (!tuple.Item1)
                    {
                        AddLog($"获取摄像头分辨率失败：{ret.Item3}");
                        return;
                    }

                    if (!tuple.Item2.Any())
                    {
                        AddLog($"摄像头({camera})分辨率为空");
                        return;
                    }

                    VideoOptionList.Clear();
                    VideoOptionList.AddRange(tuple.Item2);
                    SelectedVideoOptionIndex = 0;
                }), ret);
            });
        }

        private void OnTimerTick(object? sender, EventArgs e)
        {
            var span = DateTime.Now - _recordStartTime;
            VideoInfo = $@"正在录制视频：{span.Hours:00}:{span.Minutes:00}:{span.Seconds:00}";
        }

        private void AddLog(string text)
        {
            text = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} {text}";
            Log = string.IsNullOrWhiteSpace(Log) ? text : $@"{text}{Environment.NewLine}{Log}";
        }
    }
}
